package com.marketlive.system.encryption;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.EncoderException;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.marketlive.system.encryption.IKeyStore;
import org.marketlive.system.encryption.IKeyStoreManager;

import java.io.IOException;
import java.security.Security;
import java.sql.Timestamp;
import java.util.List;
import java.util.Iterator;

/**
 * Implementation of the IKeyStoreManager interface.
 */
public class KeyStoreManager extends HibernateDaoSupport implements IKeyStoreManager {
    private static Log log = LogFactory.getLog(KeyStoreManager.class);
    
    /** The current key store being used i.e. the one that is active in the database. */
    private IKeyStore currentKeyStore;
    
    /** Key code for the current key store */
    private String keyCode;
    
    /** String that uniquely identifies the security provider */
    private String providerInfo;
    
    /**
     * Get the key code for the active key store.
     * @return Key code as a string.
     */
    public String getKeyCode() {
    	String keyCode = null;
    	keyCode = this.currentKeyStore.getCode();
    	return keyCode;
    }
    
    /**
     * Set key code for the active key store.
     * @param keyCode The key code to set for the active key store.
     */
    public void setKeyCode(String keyCode) {
		if (keyCode != null) {
			this.currentKeyStore.setCode(keyCode);
			this.keyCode = keyCode;
		}
	}    
    
    /**
     * Set provider info via Spring injection.
     */
	public void setProviderInfo(String providerInfo) {
		this.providerInfo = providerInfo;
	}    
    
    /**
     * Get provider info as injected by Spring.
     */
	public String getProviderInfo() {
		return providerInfo;
	}    
          
	/**
     * Init method to setup encryption/decryption.
     * Ensure there is an active keystore and only one in the persistence layer.
     * Ensure there is a KEK and that this class has access to it.
     */
    public void init() {


        try {
            //init the Bouncy Castle provider
        	BouncyCastleProvider bcp = new BouncyCastleProvider();
        	String packageInfo = bcp.getInfo();
        	
        	if (null == providerInfo) {
        		throw new RuntimeException("No security provider configured.");
        	}
        	
        	if (!providerInfo.equals(packageInfo)) {
        		throw new RuntimeException(
        			"The version of the security provider is not the same as the one configured. The configured security provider should be: " 
        				+ providerInfo);
        	}
        	
            Security.addProvider(bcp);
                     
            IKeyStore keyStore = this.findActiveKeystore();
            
            if (keyStore == null) {
            	throw new RuntimeException("Error during initializaton: No active keystore or more than one active keystore found in the database.");
            }  
            
            this.setCurrentKeyStore(keyStore);
            setKeyCode(this.getKeyCode());
            if (null == keyCode) {
            	throw new RuntimeException("No key code configured for the key store.");
            }
            if (log.isDebugEnabled()) {
            	log.debug("Key code found = " + keyCode);
            }            
            
        } catch (Exception e) {
            throw new RuntimeException("Error during initialization of encryption/decryption support.", e);
        }

    }    

	/** 
	 * Encode byte array to Base64 string.
	* @param bytes
	* @return Encoded string
	*/
    public String encodeBase64(byte[] bytes) throws EncoderException {
    	String encodedString = null;
    	byte[] encodedBytes = Base64.encodeBase64(bytes);
		encodedString = new String(encodedBytes);
		return encodedString;
	}
	
	/**
	* Decode Base64 encoded string to byte array.
	* @param text The string
	* @return Bytes array
	* @throws IOException
	*/
	public byte[] decodeBase64(String inString) throws DecoderException {
		byte [] decodedBytes = null;
		String blankStr = makeBlank(inString);
		decodedBytes = Base64.decodeBase64(blankStr.getBytes());
		return decodedBytes;
	} 
	
    /**
     * This method makes a null String object or "null" string a blank string.
     * @param inString java.lang.String
     * @return java.lang.String
     */
    public String makeBlank(String inString) {

        if (inString == null) {
            return "";
        } else if (inString.equals("null")) {
            return "";
        } else {
            return inString;
        }
    }     

    /**
     * Create a KeyStore object with the given code, privateKey, publicKey, and isSymmetric status.
     *
     * If isSymmetric = true, then privateKey = password encryption/decryption key, publicKey = null
     * If isSymmetric = false, then privateKey = ccNum decryption key, publicKey = ccNum encryption key.
     *
     * @param code  The unique identifier for this object.
     * @param privateKey a byte array with private key data.
     * @param publicKey  a byte array with the public key data.
     * @param isPasswordKey set to true if the private key sent is a password encryption/decryption key.
     */
    public void createKeyStore(String code, String privateKey, String publicKey, boolean isPasswordKey, boolean isActive) {
        IKeyStore keyStore = new KeyStore(code);
        keyStore.setDateCreated(new Timestamp(System.currentTimeMillis()));
        keyStore.setPrivateKey(privateKey);
        keyStore.setPublicKey(publicKey);
        keyStore.setActive(isActive);
        getHibernateTemplate().save(keyStore);
    }

    /**
     * Find the KeyStore object with the given key code.
     *
     * @param code a string identifier for the key code.
     * @return returns a keyStore object.
     */
    public IKeyStore findByCode(String code) {
        List keyStoreList = getHibernateTemplate().find("from KeyStore where key_code = ?", code);
        IKeyStore keyStore = null;
        if (keyStoreList != null && keyStoreList.size() > 0) {
            if (keyStoreList.size() > 1) {
                log.error("KeyStore code is not unique = " + code);
            }
            keyStore = (IKeyStore) keyStoreList.get(0); // grab the first one found.
        }
        return keyStore;
    }

    /**
     * Find the KeyStore object which is active (STATUS = 1).
     *
     * @return returns a keyStore object or null if no active keystore is found.
     */
    public IKeyStore findActiveKeystore() {
        List keyStoreList = getHibernateTemplate().find("from KeyStore where active = 1");
        IKeyStore keyStore = null;
        if (keyStoreList != null && keyStoreList.size() > 0) {
            if (keyStoreList.size() == 1) {                
                keyStore = (IKeyStore) keyStoreList.get(0);
            } else {
            	log.error("More than one active keystore!");
            }
        }
        return keyStore;
    }

    /**
     * Find all KeyStore objects.
     *
     * @return returns a list keyStore objects.
     */
    public List findAll() {
        List keyStoreList = getHibernateTemplate().find("from KeyStore");
        return keyStoreList;
    }

    /**
     * Delete the given keyStore object.
     * @param keyStore the KeyStore object to delete.
     */
    public void removeKeyStore(IKeyStore keyStore) {
        getHibernateTemplate().delete(keyStore);
    }

    /**
     * Update the given KeyStore object.
     * @param keyStore the KeyStore object to update.
     */
    public void updateKeyStore(IKeyStore keyStore) {
        getHibernateTemplate().saveOrUpdate(keyStore);
    }

    /**
     * Make the given keystore active and deactive all others.
     * @param keyStore the KeyStore object to activate.
     */
    public void updateActiveStatus(IKeyStore keyStore) {
        List keyStoreList = findAll();
        if (keyStoreList != null)  {
            Iterator iter = keyStoreList.iterator();
            while (iter.hasNext()) {
                IKeyStore curKeyStore = (IKeyStore) iter.next();
                if (keyStore.equals(curKeyStore)) {
                    curKeyStore.setActive(true);
                } else {
                    curKeyStore.setActive(false);
                }
                getHibernateTemplate().saveOrUpdate(curKeyStore);
            }
        }
    }
    
    /**
     * Once the keystore is initialized set the current keystore.
     * @param currentKeyStore The keystore initialized or newly created.
     */
    public void setCurrentKeyStore(IKeyStore currentKeyStore) {
    	this.currentKeyStore = currentKeyStore;
    }
    
    /**
     * Get the current keystore.
     * @return The current active keystore.
     */
    public IKeyStore getCurrentKeyStore() {
    	return this.currentKeyStore;
    }
    
    /**
     * Returns the public key for the active key store.
     * @return Public key as a String.
     */
    public String getPublicKey() {
    	String publicKey = null;
    	publicKey = this.currentKeyStore.getPublicKey();
    	return publicKey;
    }
    
    /**
     * Get the private key for the active key store.
     * @return  Private key as a String.
     */
    public String getPrivateKey() {
    	String privateKey = null;
    	privateKey = this.currentKeyStore.getPrivateKey();
    	return privateKey;    	
    }           
    
}
